import JSEncrypt from "jsencrypt"
import {getByteSizeUtf8} from "st-common-core"

/**
 * RSA 密钥对密钥 bit 数的类型
 */
export type RsaKeySizeType = 1024 | 2048

/**
 * RSA 密钥对密钥的 bit 数，即密钥的长度。<br/>
 * 常用的值有 1024、2048，密钥的 bit 长度越长越安全，但是对数据的加密的消耗也越大。<br/>
 * 这里，默认密钥的 bit 长度为 1024 bit。
 */
let keySize: RsaKeySizeType = 1024

/**
 * 在 keySize bit 长度下的 RSA 密钥所能够加密明文的最大长度。<br/>
 * RSA 加密算法一次能加密的明文最大长度与密钥长度(RSA 密钥的 bit 数)成正比，jsencrypt 中默认使用的
 * Padding 方式为 PKCS1，RSA 加密算法会使用 11 字节(11 * 8 bit)的长度用于填充，所以 RSA 加密算
 * 法所能够加密的最大明文长度为 (keySize / 8 - 11) byte
 */
let plainTextMaxLen = 117

/**
 * 在 keySize bit 长度下的 RSA 密钥所能够解密密文的最大长度。<br/>
 * RSA 加密算法所能够解密的最大密文长度的计算公式为 (keySize / 8) byte
 */
// let secretTextMaxLen = 128

/**
 * 修改 RSA 密钥对密钥的 bit 数(密钥的长度)<br/>
 * 此方法在修改 RSA 密钥对密钥的 bit 数后，会重新计算在新的 keySize bit 长度下 RSA 密钥对密钥所能够
 * 加密的最大明文长度以及所能够解密的最大密文长度
 *
 * @param size 新的 RSA 密钥对密钥的 bit 数(密钥的长度)
 */
export const setKeySize = (size: RsaKeySizeType) => {
  keySize = size
  plainTextMaxLen = keySize / 8 - 11
  // secretTextMaxLen = keySize / 8
}

/**
 * RSA 密钥对属性名称枚举
 */
export enum RsaKeyPairPropNameEnum {
  // 公钥
  PUB_KEY = 'publicKey',
  // 私钥
  PRI_KEY = 'privateKey',
}

/**
 * RSA 密钥对属性名称常量对象
 */
export const RsaKeyPairPropName = {
  PUB_KEY: 'publicKey',
  PRI_KEY: 'privateKey',
} as const

/**
 * 获取 RSA 密钥对<br/>
 * 需要注意的是，返回的 RSA 密钥对中的公钥和私钥字符串都是带 header 和 footer 的
 */
export const getKeyPair = () => {
  let jsEncrypt = new JSEncrypt({
    default_key_size: keySize.toString()
  })
  // 获取 RSA 密钥对象
  let RsaKey = jsEncrypt.getKey()
  // 获取公钥和私钥字符串
  let pubKey = RsaKey.getPublicKey()
  let priKey = RsaKey.getPrivateKey()
  return {
    [RsaKeyPairPropNameEnum.PUB_KEY]: pubKey,
    [RsaKeyPairPropNameEnum.PRI_KEY]: priKey,
  }
}

/**
 * 获取 base64 编码的 RSA 密钥对<br/>
 * 需要注意的是，返回的 RSA 密钥对中的公钥和私钥字符串都是<span style="color: red">不</span>带 header 和 footer 的
 */
export const getKeyPairB64 = () => {
  let jsEncrypt = new JSEncrypt({
    default_key_size: keySize.toString()
  })
  // 获取 RSA 密钥对象
  let RsaKey = jsEncrypt.getKey()
  // 获取 base64 编码的公钥和私钥字符串
  let pubKeyB64 = RsaKey.getPublicBaseKeyB64()
  let priKeyB64 = RsaKey.getPrivateBaseKeyB64()
  return {
    [RsaKeyPairPropNameEnum.PUB_KEY]: pubKeyB64,
    [RsaKeyPairPropNameEnum.PRI_KEY]: priKeyB64,
  }
}

/**
 * 判断是否继续执行后续的 RSA 加密或解密<br/>
 * 只有当需要加密或解密的文本和密钥字符串都不为空字符串，且需要加密处理的明文字符串不超过所能够加密明文的最大长度，
 * 才继续执行后续的 RSA 加密或解密
 *
 * @param text 需要加密或解密的文本
 * @param keyStr 密钥字符串
 * @param isDecrypt 是否为加密
 */
const isRsa = (text: string, keyStr: string, isDecrypt: boolean = false) => {
  if (text.length <= 0 && keyStr.length <= 0) return false
  // 需要加密处理的明文字符串超过所能够加密明文的最大长度
  if (!isDecrypt && getByteSizeUtf8(text) > plainTextMaxLen) return false
  return true
}

/**
 * 使用 RSA 公钥对明文进行加密<br/>
 * <span style="color: orange">当需要加密的明文字符串或 RSA 公钥字符串为空字符串，或者需要加密处理的明文字符串
 * 超过所能够加密明文的最大长度，以及加密失败等情况，都会返回传递进来的明文字符串</span>；否则，返回加密后的密文字
 * 符串
 *
 * @param plainText 明文
 * @param publicKey RSA 公钥
 * @return {string} 加密后的密文
 */
export const encryptByPubKey = (plainText: string, publicKey: string): string => {
  // 无法继续执行后续的 RSA 加密，直接返回明文
  if (!isRsa(plainText, publicKey)) return plainText
  // 加密器
  const encryptor = new JSEncrypt({
    default_key_size: keySize.toString()
  })
  // 设置公钥
  encryptor.setPublicKey(publicKey)
  // 加密
  let res = encryptor.encrypt(plainText)
  // 加密失败
  if (!res) return plainText
  return res
}

/**
 * 使用 RSA 私钥对密文进行解密<br/>
 * <span style="color: orange">当需要解密的密文字符串或 RSA 私钥字符串为空字符串，或者解密失败等情况，都会直接返回
 * 传递进来的密文字符串</span>；否则，返回解密后的密文字符串
 *
 * @param secretText 密文
 * @param privateKey 密钥
 * @return {string} 密文解密后的明文
 */
export const decryptByPriKey = (secretText: string, privateKey: string): string => {
  // 无法继续执行后续的 RSA 解密，直接返回密文
  if (!isRsa(secretText, privateKey, true)) return secretText
  // 解密器
  const decryptor = new JSEncrypt({
    default_key_size: keySize.toString()
  })
  // 设置私钥
  decryptor.setPrivateKey(privateKey)
  // 解密
  let res = decryptor.decrypt(secretText)
  // 解密失败
  if (!res) return secretText
  return res
}
